﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media.Animation;

namespace Gmantis.Controls.Chart
{
    public class ActionCollection : System.Collections.ObjectModel.ObservableCollection<Action>
    {
        // Fields
        private Action _activeAction;
        private ActionAdorner _adorner;
        private Axis _ax;
        private Dictionary<Axis, Point> _axisStates = new Dictionary<Axis, Point>();
        private Axis _ay;
        private Chart _chart;
        private Point _pt0 = new Point();
        private Rect _rect = new Rect();
        private double _scalex;
        private double _scaley;
        private ActionType _state;
        private Storyboard _stb;
        private bool _stb_finished;
        private double _updateDelay = 40.0;
        private double _valx;
        private double _valy;
        private const double minScale = 1E-05;

        // Methods
        internal ActionCollection(Chart chart)
        {
            this._chart = chart;
        }

        private void _stb_Completed(object sender, EventArgs e)
        {
            this.UpdateAxesDirect();
            this._stb_finished = true;
        }

        internal void BeginManipulation(Point pt)
        {
            this._ax = this.GetActiveAxisX(pt.X, AxisType.X);
            this._ay = this.GetActiveAxisX(pt.Y, AxisType.Y);
            this._valx = this._ax.Value;
            this._valy = this._ay.Value;
            this._scalex = this._ax.Scale;
            this._scaley = this._ay.Scale;
        }

        private ActionType FindAction(MouseButtonEventArgs e)
        {
            this._activeAction = null;
            ActionType none = ActionType.None;
            int count = base.Count;
            Point position = this.GetPosition(e);
            if (this.Chart.View.PlotRect.Contains(position))
            {
                for (int i = 0; i < count; i++)
                {
                    Action action = base[i];
                    if (Keyboard.Modifiers == action.Modifiers)
                    {
                        none = action.ActionType;
                        this._activeAction = action;
                        return none;
                    }
                }
            }
            return none;
        }

        internal void FireEnter()
        {
            if (this.Chart != null)
            {
                this.Chart.FireActionEnter(this._activeAction, EventArgs.Empty);
            }
        }

        internal void FireLeave()
        {
            if (this.Chart != null)
            {
                this.Chart.FireActionLeave(this._activeAction, EventArgs.Empty);
            }
        }

        internal Action GetAction(ActionType atype)
        {
            foreach (Action action in this)
            {
                if (action.ActionType == atype)
                {
                    return action;
                }
            }
            return null;
        }

        private Axis GetActiveAxisX(double val, AxisType type)
        {
            foreach (Axis axis in this._chart.View.Axes)
            {
                if (axis.AxisType == type)
                {
                    Canvas iax = (Canvas)axis.iax;
                    double num = (type == AxisType.X) ? Canvas.GetLeft(iax) : Canvas.GetTop(iax);
                    double num2 = num + iax.Width;
                    if ((num <= val) && (val <= num2))
                    {
                        return axis;
                    }
                }
            }
            if (type == AxisType.X)
            {
                return this._chart.View.AxisX;
            }
            return this._chart.View.AxisY;
        }

        private Point GetPosition(MouseEventArgs e)
        {
            return e.GetPosition(this.Chart.ViewElement);
        }

        internal void OnMouseDown(MouseButtonEventArgs e)
        {
            if ((this.State == ActionType.None) && (this.Chart.Data.Renderer is Renderer2D))
            {
                ActionType type = this.FindAction(e);
                if (type != ActionType.None)
                {
                    this._pt0 = this.GetPosition(e);
                    if (this.Chart.TryCaptureMouse())
                    {
                        this.State = type;
                        this._ax = this.GetActiveAxisX(this._pt0.X, AxisType.X);
                        this._ay = this.GetActiveAxisX(this._pt0.Y, AxisType.Y);
                        this._valx = this._ax.Value;
                        this._valy = this._ay.Value;
                        this._scalex = this._ax.Scale;
                        this._scaley = this._ay.Scale;
                        this.Adorner.Shape.Width = 0.0;
                        this.Adorner.Shape.Height = 0.0;
                        if (!this.Chart.View.Children.Contains(this.Adorner.Shape))
                        {
                            this.Chart.View.Children.Add(this.Adorner.Shape);
                        }
                        if (type == ActionType.Zoom)
                        {
                            ZoomAction action = this.GetAction(ActionType.Zoom) as ZoomAction;
                            if (action != null)
                            {
                                if (action.Fill != null)
                                {
                                    this.Adorner.Shape.Fill = action.Fill;
                                }
                                if (action.Stroke != null)
                                {
                                    this.Adorner.Shape.Stroke = action.Stroke;
                                }
                            }
                        }
                        this.FireEnter();
                    }
                }
            }
        }

        internal void OnMouseMove(MouseEventArgs e)
        {
            if ((this.State == ActionType.None) || (this.State == ActionType.Pinch))
            {
                if (this.Chart.View.Children.Contains(this.Adorner.Shape))
                {
                    this.Chart.View.Children.Remove(this.Adorner.Shape);
                }
            }
            else
            {
                Point position = this.GetPosition(e);
                switch (this.State)
                {
                    case ActionType.Translate:
                        this.PerformTranslate(this._pt0, position);
                        this._pt0 = position;
                        break;

                    case ActionType.Scale:
                        this.PerformScale(this._pt0, position);
                        this._pt0 = position;
                        break;
                }
                double x = Math.Min(this._pt0.X, position.X);
                Point point2 = new Point(x, Math.Min(this._pt0.Y, position.Y));
                double introduced13 = Math.Max(this._pt0.X, position.X);
                Point point3 = new Point(introduced13, Math.Max(this._pt0.Y, position.Y));
                if ((point2.X != point3.X) && (point2.Y != point3.Y))
                {
                    Canvas iax = (Canvas)this._ax.iax;
                    Canvas element = (Canvas)this._ay.iax;
                    Rect rect = new Rect(Canvas.GetLeft(iax), Canvas.GetTop(element), iax.Width, element.Width);
                    if (this.Chart.View.AxisX.MinScale == this.Chart.View.AxisX.Scale)
                    {
                        point2.X = rect.Left;
                        point3.X = rect.Right;
                    }
                    if (this.Chart.View.AxisY.MinScale == this.Chart.View.AxisY.Scale)
                    {
                        point2.Y = rect.Top;
                        point3.Y = rect.Bottom;
                    }
                    this.Rect = new Rect(point2.X, point2.Y, point3.X - point2.X, point3.Y - point2.Y);
                    Rect rect2 = this.Rect;
                    if (rect2.X < rect.X)
                    {
                        rect2.Width = Math.Max((double)0.0, (double)(rect2.Width - (rect.X - rect2.X)));
                        rect2.X = rect.X;
                    }
                    if (rect2.Y < rect.Y)
                    {
                        rect2.Height = Math.Max((double)0.0, (double)(rect2.Height - (rect.Y - rect2.Y)));
                        rect2.Y = rect.Y;
                    }
                    if (rect2.Right > rect.Right)
                    {
                        rect2.Width = Math.Max((double)0.0, (double)(rect2.Width - (rect2.Right - rect.Right)));
                    }
                    if (rect2.Bottom > rect.Bottom)
                    {
                        rect2.Height = Math.Max((double)0.0, (double)(rect2.Height - (rect2.Bottom - rect.Bottom)));
                    }
                    this.Rect = rect2;
                    this.Adorner.Shape.Width = this.Rect.Width;
                    this.Adorner.Shape.Height = this.Rect.Height;
                    Canvas.SetLeft(this.Adorner.Shape, this.Rect.Left);
                    Canvas.SetTop(this.Adorner.Shape, this.Rect.Top);
                }
            }
        }

        internal void OnMouseUp(MouseButtonEventArgs e)
        {
            if (this.State != ActionType.None)
            {
                this.Chart.ReleaseMouseCapture();
                if (this.State == ActionType.Zoom)
                {
                    Point position = this.GetPosition(e);
                    if ((position.X != this._pt0.X) && (position.Y != this._pt0.Y))
                    {
                        this.PerformZoom(this._pt0, position);
                    }
                    this._pt0 = position;
                }
                if (this.Chart.View.Children.Contains(this.Adorner.Shape))
                {
                    this.Chart.View.Children.Remove(this.Adorner.Shape);
                }
                this.State = ActionType.None;
                this.FireLeave();
            }
        }

        internal void OnMouseWheel(System.Windows.Input.MouseWheelEventArgs e)
        {
            if (this.State == ActionType.None)
            {
                int count = base.Count;
                for (int i = 0; i < count; i++)
                {
                    Action action = base[i];
                    if (action.SupportMouseWheel && (action.Modifiers == Keyboard.Modifiers))
                    {
                        this._activeAction = action;
                        this.FireEnter();
                        if (action is ScaleAction)
                        {
                            this.PerformScale((ScaleAction)action, (double)e.Delta);
                        }
                        else if (action is TranslateAction)
                        {
                            this.PerformTranslate((TranslateAction)action, (double)e.Delta);
                        }
                        this.FireLeave();
                        return;
                    }
                }
            }
        }

        internal void PerformScale(Point pt)
        {
            Axis axis = this._ax;
            Axis axis2 = this._ay;
            this._valx = axis.Value;
            this._valy = axis2.Value;
            this._scalex = axis.Scale;
            this._scaley = axis2.Scale;
            pt = this.PointToData(pt);
            double valx = (pt.X - axis.ActualMin) / (axis.ActualMax - axis.ActualMin);
            double valy = (pt.Y - axis2.ActualMin) / (axis2.ActualMax - axis2.ActualMin);
            double num3 = 0.5;
            double scalex = this._scalex * num3;
            if (scalex > 1.0)
            {
                scalex = 1.0;
            }
            if (scalex < 1E-05)
            {
                scalex = 1E-05;
            }
            double scaley = this._scaley * num3;
            if (scaley > 1.0)
            {
                scaley = 1.0;
            }
            if (scaley < 1E-05)
            {
                scaley = 1E-05;
            }
            this.UpdateAxes(scalex, valx, scaley, valy, false);
        }

        private void PerformScale(ScaleAction sa, double delta)
        {
            if (sa != null)
            {
                Axis axisX = this.Chart.View.AxisX;
                Axis axisY = this.Chart.View.AxisY;
                double num = 1.0 - sa.MouseWheelFactor;
                if (num >= 1.0)
                {
                    num = 0.95;
                }
                else if (num <= 0.0)
                {
                    num = 0.05;
                }
                double scale = axisX.Scale;
                double valx = (scale == 1.0) ? 0.5 : axisX.Value;
                if ((sa.MouseWheelDirection & MouseWheelDirection.X) > MouseWheelDirection.None)
                {
                    if (delta > 0.0)
                    {
                        scale *= num;
                    }
                    else
                    {
                        scale /= num;
                    }
                }
                double scaley = axisY.Scale;
                double valy = (scaley == 1.0) ? 0.5 : axisY.Value;
                if ((sa.MouseWheelDirection & MouseWheelDirection.Y) > MouseWheelDirection.None)
                {
                    if (delta > 0.0)
                    {
                        scaley *= num;
                    }
                    else
                    {
                        scaley /= num;
                    }
                }
                this.UpdateAxes(scale, valx, scaley, valy, true);
            }
        }

        private void PerformScale(Point pt0, Point pt1)
        {
            Axis axis = this._ax;
            Axis axis2 = this._ay;
            Rect plotRect = this.Chart.View.PlotRect;
            double num1 = (pt1.X - pt0.X) / plotRect.Width;
            double y = (pt1.Y - pt0.Y) / plotRect.Height;
            if (y != 0.0)
            {
                double num2 = Math.Pow(10.0, y);
                double scalex = this._scalex * num2;
                if (scalex > 1.0)
                {
                    scalex = 1.0;
                }
                if (scalex < 1E-05)
                {
                    scalex = 1E-05;
                }
                double scaley = this._scaley * num2;
                if (scaley > 1.0)
                {
                    scaley = 1.0;
                }
                if (scaley < 1E-05)
                {
                    scaley = 1E-05;
                }
                this.UpdateAxes(scalex, axis.Value, scaley, axis2.Value, false);
            }
        }

        internal void PerformScale(Point pt, double xfactor, double yfactor)
        {
            Axis axis = this._ax;
            Axis axis2 = this._ay;
            this._valx = axis.Value;
            this._valy = axis2.Value;
            this._scalex = axis.Scale;
            this._scaley = axis2.Scale;
            pt = this.PointToData(pt);
            double valx = (pt.X - axis.ActualMin) / (axis.ActualMax - axis.ActualMin);
            double valy = (pt.Y - axis2.ActualMin) / (axis2.ActualMax - axis2.ActualMin);
            double scalex = this._scalex / xfactor;
            if (scalex > 1.0)
            {
                scalex = 1.0;
            }
            if (scalex < 1E-05)
            {
                scalex = 1E-05;
            }
            double scaley = this._scaley / yfactor;
            if (scaley > 1.0)
            {
                scaley = 1.0;
            }
            if (scaley < 1E-05)
            {
                scaley = 1E-05;
            }
            this.UpdateAxes(scalex, valx, scaley, valy, false);
        }

        private void PerformTranslate(TranslateAction ta, double delta)
        {
            if (ta != null)
            {
                Axis axis = this._ax;
                Axis axis2 = this._ay;
                double mouseWheelFactor = ta.MouseWheelFactor;
                double scale = axis.Scale;
                double valx = axis.Value;
                if (((ta.MouseWheelDirection & MouseWheelDirection.X) > MouseWheelDirection.None) && (scale < 1.0))
                {
                    double num4 = axis.Reversed ? -delta : delta;
                    if (num4 > 0.0)
                    {
                        valx -= mouseWheelFactor * scale;
                    }
                    else
                    {
                        valx += mouseWheelFactor * scale;
                    }
                }
                double scaley = axis2.Scale;
                double valy = axis2.Value;
                if (((ta.MouseWheelDirection & MouseWheelDirection.Y) > MouseWheelDirection.None) && (scaley < 1.0))
                {
                    double num7 = axis2.Reversed ? -delta : delta;
                    if (num7 > 0.0)
                    {
                        valy += mouseWheelFactor * scaley;
                    }
                    else
                    {
                        valy -= mouseWheelFactor * scaley;
                    }
                }
                this.UpdateAxes(scale, valx, scaley, valy, true);
            }
        }

        internal void PerformTranslate(Point pt0, Point pt1)
        {
            Axis ax = this._ax;
            Axis axis2 = this._ay;
            Rect plotRect = this.Chart.View.PlotRect;
            double num = (pt1.X - pt0.X) / plotRect.Width;
            double delta = (pt1.Y - pt0.Y) / plotRect.Height;
            if ((num != 0.0) || (delta != 0.0))
            {
                if (num != 0.0)
                {
                    this._valx = TranslateUpdateAxis(ax, -num, this._valx);
                }
                if (delta != 0.0)
                {
                    this._valy = TranslateUpdateAxis(axis2, delta, this._valy);
                }
                this.UpdateAxes(ax.Scale, this._valx, axis2.Scale, this._valy, false);
            }
        }

        internal void PerformZoom(Point pt0, Point pt1)
        {
            Axis ax = this._ax;
            Axis axis2 = this._ay;
            pt0 = this.PointToData(pt0);
            pt1 = this.PointToData(pt1);
            Point point = new Point(Math.Min(pt0.X, pt1.X), Math.Min(pt0.Y, pt1.Y));
            Point point2 = new Point(Math.Max(pt0.X, pt1.X), Math.Max(pt0.Y, pt1.Y));
            if (double.IsNaN(point.X))
            {
                point.X = ax.ActualMin;
            }
            else
            {
                point.X = Math.Max(point.X, ax.ActualMin);
            }
            if (double.IsNaN(point2.X))
            {
                point2.X = ax.ActualMax;
            }
            else
            {
                point2.X = Math.Min(point2.X, ax.ActualMax);
            }
            if (double.IsNaN(point.Y))
            {
                point.Y = axis2.ActualMin;
            }
            else
            {
                point.Y = Math.Max(point.Y, axis2.ActualMin);
            }
            if (double.IsNaN(point2.Y))
            {
                point2.Y = axis2.ActualMax;
            }
            else
            {
                point2.Y = Math.Min(point2.Y, axis2.ActualMax);
            }
            this.Chart.BeginUpdate();
            ZoomUpdateAxis(ax, point.X, point2.X);
            ZoomUpdateAxis(axis2, point.Y, point2.Y);
            this.Chart.EndUpdate();
            this._valx = ax.Value;
            this._valy = axis2.Value;
            this._scalex = ax.Scale;
            this._scaley = axis2.Scale;
        }

        private Point PointToData(Point pt)
        {
            return this.Chart.View.PointToData(this._ax.Name, this._ay.Name, pt);
        }

        private static double TranslateUpdateAxis(Axis ax, double delta, double val)
        {
            if (ax.Scale == 1.0)
            {
                return 0.0;
            }
            double num = (ax.ActualMax - ax.ActualMin) * ax.Scale;
            double num2 = (val * ((ax.ActualMax - ax.ActualMin) - num)) + ax.ActualMin;
            double num3 = num2 + (num * delta);
            double num4 = (num3 - ax.ActualMin) / ((ax.ActualMax - ax.ActualMin) - num);
            if (num4 < 0.0)
            {
                return 0.0;
            }
            if (num4 > 1.0)
            {
                num4 = 1.0;
            }
            return num4;
        }

        private void UpdateAxes(double scalex, double valx, double scaley, double valy, bool immediate)
        {
            this._scalex = scalex;
            this._valx = valx;
            this._scaley = scaley;
            this._valy = valy;
            this._axisStates[this._ax] = new Point(scalex, valx);
            this._axisStates[this._ay] = new Point(scaley, valy);
            if (immediate || (this.UpdateDelay == 0.0))
            {
                this.UpdateAxesDirect();
            }
            else
            {
                if (this._stb == null)
                {
                    this._stb = new Storyboard();
                    this._stb.Duration = new Duration(TimeSpan.FromMilliseconds(this.UpdateDelay));
                    this._stb.Completed += new EventHandler(this._stb_Completed);
                    this._stb_finished = true;
                }
                if (this._stb_finished)
                {
                    this._stb_finished = false;
                    this._stb.Begin();
                }
            }
        }

        private void UpdateAxesDirect()
        {
            this.Chart.BeginUpdate();
            foreach (Axis axis in this._axisStates.Keys)
            {
                if (this._ax == axis)
                {
                    this._ax.Scale = this._scalex;
                    this._ax.Value = this._valx;
                }
                else if (this._ay == axis)
                {
                    this._ay.Scale = this._scaley;
                    this._ay.Value = this._valy;
                }
                else
                {
                    Point point = this._axisStates[axis];
                    axis.Scale = point.X;
                    Point point2 = this._axisStates[axis];
                    axis.Value = point2.Y;
                }
            }
            this.Chart.EndUpdate();
            this._axisStates.Clear();
        }

        private static void ZoomUpdateAxis(Axis ax, double min, double max)
        {
            if (ax.ActualMin != ax.ActualMax)
            {
                double num = (max - min) / (ax.ActualMax - ax.ActualMin);
                if (num < 1E-05)
                {
                    num = 1E-05;
                }
                double num2 = (ax.ActualMax - ax.ActualMin) * num;
                double num3 = 0.0;
                if (num != 1.0)
                {
                    num3 = (min - ax.ActualMin) / ((ax.ActualMax - ax.ActualMin) - num2);
                }
                ax.Scale = num;
                ax.Value = num3;
            }
        }

        // Properties
        private ActionAdorner Adorner
        {
            get
            {
                if (this._adorner == null)
                {
                    this._adorner = new ActionAdorner(this);
                }
                return this._adorner;
            }
        }

        internal Chart Chart
        {
            get
            {
                return this._chart;
            }
        }

        internal Rect Rect
        {
            get
            {
                return this._rect;
            }
            private set
            {
                this._rect = value;
            }
        }

        internal ActionType State
        {
            get
            {
                return this._state;
            }
            set
            {
                this._state = value;
            }
        }

        [DefaultValue(40)]
        internal double UpdateDelay
        {
            get
            {
                return this._updateDelay;
            }
            set
            {
                if (this._updateDelay != value)
                {
                    if (value < 0.0)
                    {
                        throw new ArgumentException("The value must be nonnegative.");
                    }
                    this._updateDelay = value;
                    if (this._stb != null)
                    {
                        this._stb.Duration = new Duration(TimeSpan.FromMilliseconds(this.UpdateDelay));
                    }
                }
            }
        }
    }
}
